package demo;

import ru.nsu.fit.iconveyor.conveyor.node.InnerNode;
import ru.nsu.fit.iconveyor.conveyor.node.NodeConnection;

import java.util.*;

/**
 * @author Timur Zolotuhin (tzolotuhin@gmail.com)
 */
public class ReduceNode extends InnerNode {
    public static final String INPUT_ARG = "input_arg";
    public static final String INPUT_ARG_LOOP = "input_arg_loop";
    public static final String OUTPUT_ARG_LOOP = "output_arg_loop";
    public static final String OUTPUT_RESULT = "output_result";

    public static final String SPLIT_ROW_COUNT_PARAMETER = "SPLIT_ROW_COUNT_PARAMETER";
    public static final String SPLIT_COLUMN_COUNT_PARAMETER = "SPLIT_COLUMN_COUNT_PARAMETER";

    private final NodeConnection<Image> inputArg;
    private final NodeConnection<ArrayDeque> inputArgLoop;
    private final NodeConnection<ArrayDeque> outputArgLoop;
    private final NodeConnection<int[][]> outputResult;

    public ReduceNode(String id) {
        super(id);

        inputArg = new NodeConnection<Image>(Image.class, NodeConnection.Type.INPUT, INPUT_ARG);
        inputArgLoop = new NodeConnection<ArrayDeque>(ArrayDeque.class, NodeConnection.Type.INPUT, INPUT_ARG_LOOP);
        outputArgLoop = new NodeConnection<ArrayDeque>(ArrayDeque.class, NodeConnection.Type.OUTPUT, OUTPUT_ARG_LOOP);
        outputResult = new NodeConnection<int[][]>(int[][].class, NodeConnection.Type.OUTPUT, OUTPUT_RESULT);

        addNodeConnection(inputArg);
        addNodeConnection(inputArgLoop);
        addNodeConnection(outputArgLoop);
        addNodeConnection(outputResult);
    }

    @Override
    public Map<String, Object> execute(Map<String, Object> inputBundles, Properties prop) {
        Map<String, Object> result = new HashMap<String, Object>();

        ArrayDeque<Image> chunks = new ArrayDeque<Image>();
        if (inputBundles.get(INPUT_ARG_LOOP) != null) {
            Object inputArgLoopBundle = inputBundles.get(INPUT_ARG_LOOP);
            if (inputArgLoopBundle != null && inputArgLoopBundle.getClass() == ArrayDeque.class) {
                chunks = (ArrayDeque<Image>)inputArgLoopBundle;
            }
        }

        if (inputBundles.get(INPUT_ARG) != null) {
            Object inputArgBundle = inputBundles.get(INPUT_ARG);
            if (inputArgBundle != null && inputArgBundle.getClass() == Image.class) {
                chunks.add((Image)inputArgBundle);
            }
        }

        // check chunks.size()
        int splitRowCount = 1;
        int splitColumnCount = 1;

        Object rowObj = getParameter(SPLIT_ROW_COUNT_PARAMETER);
        Object columnObj = getParameter(SPLIT_COLUMN_COUNT_PARAMETER);

        try {
            if (rowObj != null)
                splitRowCount = new Integer(rowObj.toString());
            if (columnObj != null)
                splitColumnCount = new Integer(columnObj.toString());
        } catch (Exception ex) {}

        if (splitColumnCount < 1)
            splitColumnCount = 1;
        if (splitRowCount < 1)
            splitRowCount = 1;

        if (chunks.size() == splitColumnCount * splitRowCount) {
            int outputSizeX = 0;
            int outputSizeY = 0;

            int sizeX = 0;
            int sizeY = 0;
            String name = chunks.peek().getName();

            Iterator<Image> iter = chunks.iterator();
            for (int i = 0; i < splitColumnCount; i++) {
                for (int j = 0; j < splitRowCount; j++) {
                    Image image = iter.next();
                    if (j == 0)
                        sizeX += image.getBitmap().length;
                    if (i == 0 && image.getBitmap().length > 0)
                        sizeY += image.getBitmap()[0].length;
                    if (i == 0 && j == 0) {
                        outputSizeX = image.getBitmap().length;
                        if (outputSizeX > 0)
                            outputSizeY = image.getBitmap()[0].length;
                    }
                }
            }


            int[][] imageBitmap = new int[sizeX][sizeY];

            for (int i = 0; i < splitColumnCount; i++) {
                for (int j = 0; j < splitRowCount; j++) {
                    Image chunkImage = chunks.poll();
                    int[][] chunkBitmap = chunkImage.getBitmap();
                    for (int x = 0; x < chunkBitmap.length; x++) {
                        for (int y = 0; y < chunkBitmap[x].length; y++) {
                            imageBitmap[x + i * outputSizeX][y + j * outputSizeY] = chunkBitmap[x][y];
                        }
                    }
                }
            }
            Image image = new Image();
            image.setName(name);
            image.setBitmap(imageBitmap);
            result.put(OUTPUT_RESULT, image);
        } else {
            result.put(OUTPUT_ARG_LOOP, chunks);
        }

        return result;
    }

    @Override
    public boolean isParallelExecute() {
        return false;
    }

    @Override
    public Map<String, Object> preExecute(Map<String, Object> availableInputBundles, Properties prop) {
        if (availableInputBundles.get(INPUT_ARG) != null) {
            return availableInputBundles;
        }
        return null;
    }

    @Override
    public int getOutputToInputRatio() {
        //TODO: check
        return 2;
    }
}
